﻿using System.Reflection;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using AutoMapper;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.EntityFrameworkCore;
using Quartz;
using Serilog;
using WT.OpenIddict.Domain;
using WT.OpenIddict.Domain.Roles;
using WT.OpenIddict.Domain.Services;
using WT.OpenIddict.Domain.Services.Exceptions;
using WT.OpenIddict.Domain.User;
using WT.OpenIddict.EntityFrameWorkCore;
using WT.OpenIddict.WebApi.AutoFac;
using WT.OpenIddict.WebApi.OpenIddictGrantTypeExtension;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace WT.OpenIddict.WebApi;

public class Program
{
    private static string[] defaultOrigin = [];

    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        var configuration = builder.Configuration;
        builder.WebHost.UseUrls($"http://*:{configuration["Port"]}");

        defaultOrigin = configuration["OrginCors"].Split(",");

        // Add services to the container.
        //配置项注入单例
        builder.Services.AddSingleton<IConfiguration>(builder.Configuration);
        //AutoFac注入
        ConfigureServiceProvider(builder.Host);
        //EFCore配置
        ConfigureEntityFrameWorkCore(builder);
        //配置权限策略
        ConfigurePermissions(builder);
        //OpenIddict配置
        ConfigureOpenIddict(builder, configuration);
        //swagger等配置
        ConfigureService(builder.Services);
        //AutoMapper配置
        ConfiguraAutoMapper(builder.Services);
        //Redis
        ConfigureRedis(builder.Services, configuration);
        //quartz集成
        ConfigureQuartz(builder.Services);
        //更新ketnel的配置
        ConfigureHttp2(builder);
        //logs
        ConfigureLogs(builder.Services);

        //设置表单的最大长度
        builder.Services.Configure<FormOptions>(options =>
        {
            options.ValueLengthLimit = OpenIddictConst.FormMaxValue;
            options.KeyLengthLimit = OpenIddictConst.FormMaxValue;
        });


        var app = builder.Build();
        //外部传递https头，让core能正确识别外部协议
        if (!app.Environment.IsDevelopment())
        {
            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor,
                RequireHeaderSymmetry = false, // 关键！允许头部不对称
                ForwardLimit = null
            });
            //定义路由前缀
            app.UsePathBase(configuration["PathBase"]);
        }
        else
        {
            //打印详细信息
            app.UseDeveloperExceptionPage();
        }

        //初始化种子数据
        InitializeSeedData(app);

        // 配置中间件管道
        app.UseUnifiedExceptionHandler(); // 添加统一异常处理

        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "IdentityService V1");
                c.RoutePrefix = "swagger";
            });
        }


        app.UseCookiePolicy();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseCors("default");

        app.UseAuthentication();
        app.UseAuthorization();

        app.MapControllers();


        //配置日志
        // 从 appsettings.json 加载配置
        // 初始化 Serilog
        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(configuration)
            .CreateLogger();

        app.Run();
    }

    private static void ConfigureLogs(IServiceCollection builderServices)
    {
        builderServices.AddLogging(b => b.AddConsole().SetMinimumLevel(LogLevel.Debug));
    }


    private static void ConfigurePermissions(WebApplicationBuilder builder)
    {
        builder.Services.AddAuthorization();
    }

    private static void ConfigureHttp2(WebApplicationBuilder builder)
    {
        var configuration = builder.Configuration;
        builder.WebHost.ConfigureKestrel(options =>
        {
            options.ListenAnyIP(int.Parse(configuration["Redirect:Port"]), o => o.Protocols =
                HttpProtocols.Http1);
            //GRPC端口
            // options.ListenAnyIP(int.Parse(configuration["GRPC:Port"]), o => o.Protocols =
            //     HttpProtocols.Http2);
        });
    }

    private static void ConfigureQuartz(IServiceCollection services)
    {
        services.AddQuartz(options =>
        {
            options.UseMicrosoftDependencyInjectionJobFactory();
            options.UseSimpleTypeLoader();
            options.UseInMemoryStore();
        });
        services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
    }

    private static void ConfigureRedis(IServiceCollection services, ConfigurationManager configuration)
    {
        //缓存配置
        services.AddStackExchangeRedisCache(opt =>
        {
            opt.Configuration = configuration["Redis:Address"];
            opt.InstanceName = configuration["Redis:InstanceName"];
        });
    }

    private static void InitializeSeedData(WebApplication app)
    {
        using (var serviceScope = app.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;
            var service = serviceScope.ServiceProvider.GetRequiredService<SeedDataBaseFactory>();
            try
            {
                var seedData = service.Create("OpenIddictSeedData");
                seedData.InitSeedData();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }

    private static void ConfiguraAutoMapper(IServiceCollection services)
    {
        var applicationAppAssembly = Assembly.Load("WT.OpenIddict.Application");
        var applicationContractsAppAssembly = Assembly.Load("WT.OpenIddict.Application.Contracts");
        var domainAppAssembly = Assembly.Load("WT.OpenIddict.Domain");
        var assemblies = Assembly.GetExecutingAssembly();
        //var referenceAssemblies = assemblies.GetReferencedAssemblies().Select(Assembly.Load).ToArray();
        var allAssenblies = new[]
            { assemblies, applicationAppAssembly, applicationContractsAppAssembly, domainAppAssembly };
        var profileTypes = allAssenblies.SelectMany(it => it.GetTypes())
            .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(Profile)))
            .ToArray();
        services.AddAutoMapper(profileTypes);
    }

    private static void ConfigureOpenIddict(WebApplicationBuilder builder, ConfigurationManager configuration)
    {
        builder.Services.AddOpenIddict()
            // 注册 OpenIddict 核心组件
            .AddCore(options =>
            {
                // 配置 OpenIddict 使用 Entity Framework Core 存储模型
                // 注意: 调用ReplaceDefaultEntities()方法以替换默认实体.
                options.UseEntityFrameworkCore().UseDbContext<OpenIddictDbContext>()
                    .ReplaceDefaultEntities<Guid>();
                options.UseQuartz()
                    .SetMinimumAuthorizationLifespan(TimeSpan.FromDays(7))
                    .SetMinimumTokenLifespan(TimeSpan.FromHours(12));
            })
            .AddServer(options =>
            {
                string isscure = null;
                if (int.Parse(configuration["Issuer:IsHttps"]) == 1)
                {
                    isscure = configuration["Issuer:Address"];
                    var pathBase = configuration["PathBase"] ?? "";
                    options.SetIssuer(new Uri(isscure));
                    options.SetAuthorizationEndpointUris($"{pathBase}/connect/authorize",
                            $"{pathBase}/connect/authorize/confirm")
                        .SetDeviceEndpointUris($"{pathBase}/connect/device")
                        .SetIntrospectionEndpointUris($"{pathBase}/connect/introspect")
                        .SetRevocationEndpointUris($"{pathBase}/connect/revocat")
                        .SetUserinfoEndpointUris($"{pathBase}/connect/userinfo")
                        .SetVerificationEndpointUris($"{pathBase}/connect/verify")
                        .SetLogoutEndpointUris($"{pathBase}/connect/logout")
                        .SetTokenEndpointUris($"{pathBase}/connect/token")
                        //配置token过期时间以及刷新token的过期时间
                        .SetAccessTokenLifetime(TimeSpan.FromHours(3))
                        .SetRefreshTokenLifetime(TimeSpan.FromDays(7))
                        //原先的jws_uri已经废弃
                        .SetCryptographyEndpointUris($"{pathBase}/.well-known/jwks");
                }
                else
                {
                    options.SetAuthorizationEndpointUris("/connect/authorize", "/connect/authorize/confirm")
                        .SetDeviceEndpointUris("/connect/device")
                        .SetIntrospectionEndpointUris("/connect/introspect")
                        .SetRevocationEndpointUris("/connect/revocat")
                        .SetUserinfoEndpointUris("/connect/userinfo")
                        .SetVerificationEndpointUris("/connect/verify")
                        .SetLogoutEndpointUris("/connect/logout")
                        .SetTokenEndpointUris("/connect/token")
                        //配置token过期时间以及刷新token的过期时间
                        .SetAccessTokenLifetime(TimeSpan.FromHours(3))
                        // .SetAccessTokenLifetime(TimeSpan.FromMinutes(0.5))
                        .SetRefreshTokenLifetime(TimeSpan.FromDays(7))
                        // .SetRefreshTokenLifetime(TimeSpan.FromMinutes(2))
                        //原先的jws_uri已经废弃
                        .SetCryptographyEndpointUris("/.well-known/jwks");
                }

                //配置交互服务地址
                options
                    //这是允许的模式
                    .AllowAuthorizationCodeFlow()
                    //这是允许两个客户端互相调用接口，但要指定范围，而且发起访问的客户端（后端）需要用这个模式获取到相应scopes的token，
                    //才能保证不同clientId的应用之间可以访问
                    .AllowClientCredentialsFlow()
                    //可以注册多个scope，用“，”隔开，注意被调用的客户端接口应注明【Authorize(scope="被注册的scope")】
                    // .RegisterScopes("")
                    .AllowDeviceCodeFlow()
                    .AllowHybridFlow()
                    .AllowImplicitFlow()
                    .AllowPasswordFlow()
                    .AllowRefreshTokenFlow()
                    .AddCustomGrantType(OpenIddictConst.WxGrantType)
                    .RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, Scopes.Phone, Scopes.OpenId,
                        Scopes.Address, Scopes.OfflineAccess)

                    #region 这里是开启JWT令牌的加密

                    ////提供给API校验Jwt令牌使用是配置
                    //.AddEncryptionKey(new SymmetricSecurityKey(
                    //              Convert.FromBase64String("DRjd/nduI3Efze123nvbNUfc/=")))

                    #endregion

                    //// 加密凭证 、注册签名
                    .AddDevelopmentEncryptionCertificate()
                    .AddDevelopmentSigningCertificate()
                    //强制客户端应用程序使用 Proof Key Code Exchange (PKCE)，授权码流程
                    .RequireProofKeyForCodeExchange()
                    //Sha256编码
                    .Configure(options => { options.CodeChallengeMethods.Add(CodeChallengeMethods.Sha256); })
                    //配置 启用通过后的后续处理
                    .UseAspNetCore().EnableStatusCodePagesIntegration()
                    .EnableAuthorizationEndpointPassthrough()
                    .EnableLogoutEndpointPassthrough()
                    .EnableTokenEndpointPassthrough()
                    .EnableUserinfoEndpointPassthrough()
                    .EnableVerificationEndpointPassthrough()
                    .DisableTransportSecurityRequirement(); // 禁用HTTPS 在开发测试环境

                options.Services.AddIdentityCore<UserDetails>();
                options.Services.AddSingleton<SignInManager<UserDetails>>();


                #region 禁用Token加密

                options.DisableAccessTokenEncryption();

                #endregion
            })
            .AddValidation(options =>
            {
                options.UseLocalServer();
                //强制授权条目验证 出于性能原因，OpenIddict 3.0在接收API请求时默认不检查授权条目的状态:即使附加的授权被撤销
                //，访问令牌也被认为是有效的
                options.EnableAuthorizationEntryValidation();
                options.EnableTokenEntryValidation(); // 启用令牌条目验证
                options.UseAspNetCore();
            })
            ;
    }


    private static void ConfigureEntityFrameWorkCore(WebApplicationBuilder builder)
    {
        var configuration = builder.Configuration;
        builder.Services.AddDbContext<OpenIddictDbContext>(options =>
        {
            // 配置 EfCore 使用 Microsoft SQL Server 数据库
            options.UseSqlServer(builder.Configuration.GetConnectionString("Default"));

            // 注册OpenIddict所需的数据实体集。
            // 注意：如果你需要替换默认的OpenIddict实体，请使用泛型重载方法
            options.UseOpenIddict<Guid>();
        });
        //配置身份验证
        builder.Services.Configure<IdentityOptions>(options =>
        {
            options.ClaimsIdentity.RoleClaimType = Claims.Role;
            options.ClaimsIdentity.EmailClaimType = Claims.Email;
            options.ClaimsIdentity.UserNameClaimType = Claims.Username;
            options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
        });
        builder.Services.AddAuthentication(options =>
            {
                options.DefaultScheme = "CustomCookie"; // 自定义 Cookie 方案
                options.DefaultAuthenticateScheme = "CustomCookie";
                options.DefaultSignInScheme = "CustomCookie";
                options.DefaultChallengeScheme = "CustomCookie";
            })
            .AddCookie("CustomCookie", options => // 自定义 Cookie 方案名称
            {
                options.Cookie.Name = "CustomAuthCookie"; // Cookie 名称
                options.ExpireTimeSpan = TimeSpan.FromDays(7); // Cookie 有效期
                options.SlidingExpiration = false; // 滑动过期
                options.Cookie.HttpOnly = true;
                if (!builder.Environment.IsDevelopment() && int.Parse(configuration["Issuer:IsHttps"]) == 1)
                {
                    options.Cookie.SameSite = SameSiteMode.None;
                    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                }
                else
                {
                    options.Cookie.SameSite = SameSiteMode.Lax;
                    options.Cookie.SecurePolicy = CookieSecurePolicy.None; // HTTP 环境
                }
            });
    }

    private static void ConfigureServiceProvider(ConfigureHostBuilder host)
    {
        host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
        host.ConfigureContainer<ContainerBuilder>(builder => { builder.RegisterModule<AutofacModule>(); });
    }


    private static void ConfigureService(IServiceCollection services)
    {
        services.AddHostedService<Worker>();
        services.AddControllers();
        services.ConfigureNonBreakingSameSiteCookies();
        // 注册Identity框架所需实体
        services.AddIdentity<UserDetails, RoleDetails>(options => { options.User.RequireUniqueEmail = false; })
            .AddEntityFrameworkStores<OpenIddictDbContext>()
            .AddSignInManager()
            .AddDefaultTokenProviders();

        //跨域策略
        services.AddCors(e =>
        {
            e.AddPolicy("default",
                e =>
                {
                    e.WithOrigins(defaultOrigin).AllowCredentials().AllowAnyHeader().AllowAnyMethod()
                        .WithExposedHeaders("Location");
                });
        });
        //账户要求
        services.Configure<IdentityOptions>(options =>
        {
            //不要求数字
            options.Password.RequireDigit = false;
            // options.Password.RequiredLength = 7;
            //不要求密码中有多少个不同的字符
            options.Password.RequiredUniqueChars = 0;
            //不要求包含非字母和数字的特殊字符
            options.Password.RequireNonAlphanumeric = false;
            //不要求包含大写字母
            options.Password.RequireUppercase = false;
            //不要求包含小写字母
            options.Password.RequireLowercase = false;
            options.ClaimsIdentity.UserNameClaimType = Claims.Name;
            options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
            options.ClaimsIdentity.RoleClaimType = Claims.Role;
        });
        //swagger文档
        services.AddEndpointsApiExplorer();
        services.AddSwaggerGen();
    }
}